package com.gw.business.analysis.task;

import com.gw.business.analysis.model.UserTrackDataAttribution;
import com.gw.business.analysis.model.UserTrackDataDetail;
import com.gw.business.analysis.service.UserTrackDataAttributionService;
import com.gw.business.analysis.service.UserTrackDataDetailService;
import com.gw.business.common.constant.TrackBehavior;
import com.gw.business.common.util.DateUtil;
import com.gw.platform.logger.Log;
import com.gw.platform.service.IService;
import org.apache.commons.lang.StringUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;


/**
 * 用户访问归因报表
 * <p>
 * 创建者:Sping
 * </p>
 * <p>
 * 创建时间:2015-03-02
 * </p>
 * <p>
 * 修改者:
 * </p>
 * <p>
 * 修改时间:
 * </p>
 * <p>
 * 修改原因：
 * </p>
 * <p>
 * 审核者:
 * </p>
 * <p>
 * 审核时间:
 * </p>
 * <p>
 * 审核意见：
 * </p>
 */
@Component
public class UserTrackDataAttributionTask {
    @Resource
    private IService defaultService;

    @Resource
    private UserTrackDataDetailService userTrackDataDetailService;
    @Resource
    public UserTrackDataAttributionService userTrackDataAttributionService;

    //通过分析 basic  初始化dateDetail表。
    @Scheduled(cron = "0 0 4 * * ?")
    public void initTrackAttributionTask() throws Exception {
        Log.info(this.getClass(),"-------------start initTrackAttributionTask 归因定时任务开始-----------");
        Date startTime = new Date();
        try {
            //昨天
            //for(int i = 30;i>0;i--) {
                this.executeAttributionTaskByDate(DateUtil.addDays(new Date(), -1));
            //}
            //今天
            //this.executeAttributionTaskByDate(new Date());
        } catch (Exception e) {
            Log.error(this.getClass(), "InitTrackAttribu insert or update:", e);
            throw e;
        }finally {
            long time = new Date().getTime() - startTime.getTime();
            Log.info(this.getClass(),"-------------end initTrackAttributionTask 归因定时任务结束  耗时："+time+"毫秒-----------");
        }

    }

    /**
     * 根据传入的时间，进行某天的归因统计
     *
     * @param dayTime 具体某天 格式为yyyy-MM-dd
     */
    public void executeAttributionTaskByDate(Date dayTime) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String dayTimeStr = simpleDateFormat.format(dayTime);
        try {
            dayTime = simpleDateFormat.parse(dayTimeStr);
        } catch (ParseException e) {
            Log.error(UserTrackDataAttributionTask.class, "不可能出现的日期异常");
        }


        //根据传入的时间，获取当天所有有模拟开户或真实开户的数据,除去已算过归因的数据
        List<UserTrackDataDetail> userTrackDataDetailList = userTrackDataDetailService.queryDataDetailByDay(dayTimeStr);

        //需要更新的详细汇总表主键集合
        List<Integer> detailIdList = new ArrayList<Integer>();
        //记录需要更新或新增的归因数据,key=date_来源_媒介
        Map<String, UserTrackDataAttribution> userTrackDataAttributionMap = new HashMap<String, UserTrackDataAttribution>();

        for (UserTrackDataDetail userTrackDataDetail : userTrackDataDetailList) {
            //统计一个会话中有多少改模拟开户，多少个真实开户
            String[] behaviorDetailTypes = userTrackDataDetail.getBehaviorDetailType().split(",");
            int demoNum = 0;
            int realNum = 0;
            for (String string : behaviorDetailTypes) {
                if (StringUtils.isBlank(string))
                    continue;
                if (string.indexOf(TrackBehavior.DEMOPRE.getValue()) == 0) {
                    ++demoNum;
                } else if (string.indexOf(TrackBehavior.REALPRE.getValue()) == 0) {
                    ++realNum;
                }
            }

            //1.只有模拟开户的模拟开户数计算规则：
            if (demoNum != 0 && realNum == 0) {

                //第一次来源媒介访问
                UserTrackDataDetail firstUserTrackDataDetail = userTrackDataDetailService.getVisitFirstData(userTrackDataDetail.getUserId());

                /*
                *判断第一次访问与现在的来源媒介访问是否为同一条
                * 如果为同一条，则记录本次来源媒介的模拟开户数为1
                 */
                if(userTrackDataDetail.getId() == firstUserTrackDataDetail.getId()){
                    //开户的来源媒介模拟开户数
                    this.userTrackDataAttributionMapAdd(userTrackDataAttributionMap, dayTime, userTrackDataDetail.getUtmcsr(),
                            userTrackDataDetail.getUtmcmd(), 1 * demoNum, 0);
                }else {
                    //中间访问数据
                   boolean noCenterFlag = this.centerDataDetailAttribute(userTrackDataAttributionMap, firstUserTrackDataDetail, userTrackDataDetail, Constants.DEMO_CENTER, demoNum, 0, realNum);
                    double demoFirst = Constants.DEMO_FIRST;
                    double demoLast = Constants.DEMO_LAST;
                    if(!noCenterFlag){
                        //没有中间访问，则第一次与最后一次各占0.5
                        demoFirst = Constants.NO_CENTER_DEMO_FIRST;
                        demoLast = Constants.NO_CENTER_DEMO_LAST;
                    }
                    //开户的来源媒介模拟开户数
                    this.userTrackDataAttributionMapAdd(userTrackDataAttributionMap, dayTime, userTrackDataDetail.getUtmcsr(),
                            userTrackDataDetail.getUtmcmd(), demoLast * demoNum, 0);
                    //第一次来源媒介访问
                    this.userTrackDataAttributionMapAdd(userTrackDataAttributionMap, firstUserTrackDataDetail.getStartTime(),
                            firstUserTrackDataDetail.getUtmcsr(), firstUserTrackDataDetail.getUtmcmd(), demoFirst * demoNum, 0);


                 }

            }

            //2.只有真实开户的真实开户数计算规则：
            if (demoNum == 0 && realNum != 0) {
                //判断此前是否拥有模拟开户，若有则返回第一次模拟开户的会话信息
                UserTrackDataDetail firstDemoDataDetail = userTrackDataDetailService.getFirstDemoDataDetail(userTrackDataDetail.getUserId(), DateUtil.getDateFormat(userTrackDataDetail.getStartTime(), "yyyy-MM-dd HH:mm:ss"));

                Double realFirstPercent = Constants.REAL_FIRST;
                Double realCenterPercent = Constants.REAL_CENTER;
                Double realLastPercent = Constants.REAL_LAST;

                if(firstDemoDataDetail == null){
                    //没有模拟开户，则查找第一次访问
                    firstDemoDataDetail = userTrackDataDetailService.getVisitFirstData(userTrackDataDetail.getUserId());
                }else {
                    realFirstPercent = Constants.DEMO_REAL_FIRST;
                    realCenterPercent = Constants.DEMO_REAL_CENTER;
                    realLastPercent = Constants.DEMO_REAL_LAST;
                }

                /*
                 *判断第一次模拟开户或访问与现在的来源媒介访问是否为同一条
                 * 如果为同一条，则记录本次来源媒介的真实开户数为1
                 */
                if(userTrackDataDetail.getId() == firstDemoDataDetail.getId()){
                    //开户的来源媒介模拟开户数
                    this.userTrackDataAttributionMapAdd(userTrackDataAttributionMap, dayTime, userTrackDataDetail.getUtmcsr(),
                            userTrackDataDetail.getUtmcmd(), 0, realNum);
                }else {
                    //中间访问
                    boolean noCenterRealFlag = this.centerDataDetailAttribute(userTrackDataAttributionMap, firstDemoDataDetail, userTrackDataDetail, 0, demoNum, realCenterPercent, realNum);

                    if(!noCenterRealFlag){
                        realFirstPercent = Constants.NO_CENTER_REAL_FIRST;
                        realLastPercent = Constants.NO_CENTER_REAL_LAST;
                    }

                    //开户的来源媒介模拟开户数
                    this.userTrackDataAttributionMapAdd(userTrackDataAttributionMap, dayTime, userTrackDataDetail.getUtmcsr(),
                            userTrackDataDetail.getUtmcmd(), 0, realLastPercent * realNum);


                    //第一次模拟开户或访问
                    this.userTrackDataAttributionMapAdd(userTrackDataAttributionMap, firstDemoDataDetail.getStartTime(),
                            firstDemoDataDetail.getUtmcsr(), firstDemoDataDetail.getUtmcmd(), 0, realFirstPercent * realNum);

                 }
            }

            //3.一次会话中即有模拟开户又有真实开户的模拟开户数、真实开户数计算规则：
            if (demoNum != 0 && realNum != 0) {
                Double realFirstPercent = Constants.REAL_FIRST;
                Double realCenterPercent = Constants.REAL_CENTER;
                Double realLastPercent = Constants.REAL_LAST;
                Double demoFirstPercent = Constants.DEMO_FIRST;
                Double demoLastPercent = Constants.DEMO_LAST;

                //判断此前是否拥有模拟开户，若有则返回第一次模拟开户的会话信息
                UserTrackDataDetail firstDemoDataDetail = userTrackDataDetailService.getFirstDemoDataDetail(userTrackDataDetail.getUserId(), DateUtil.getDateFormat(userTrackDataDetail.getStartTime(), "yyyy-MM-dd HH:mm:ss"));
                UserTrackDataDetail firstVisitDataDetail = userTrackDataDetailService.getVisitFirstData(userTrackDataDetail.getUserId());
                if(firstDemoDataDetail == null){
                    //没有模拟开户，则查找第一次访问
                    //模拟、真实归因第一次访问与中间访问一样
                    /*
                    *判断第一次模拟开户或访问与现在的来源媒介访问是否为同一条
                    * 如果为同一条，则记录本次来源媒介的真实开户数为1
                    */
                    if(userTrackDataDetail.getId() == firstVisitDataDetail.getId()){
                        //开户的来源媒介模拟开户数
                        this.userTrackDataAttributionMapAdd(userTrackDataAttributionMap, dayTime, userTrackDataDetail.getUtmcsr(),
                                userTrackDataDetail.getUtmcmd(), demoNum, realNum);
                    }else {
                        //中间访问
                        boolean noCenterFlag = this.centerDataDetailAttribute(userTrackDataAttributionMap, firstVisitDataDetail, userTrackDataDetail, Constants.DEMO_CENTER, demoNum, realCenterPercent, realNum);

                        if(!noCenterFlag){
                            realFirstPercent = Constants.NO_CENTER_REAL_FIRST;
                            realLastPercent = Constants.NO_CENTER_REAL_LAST;
                            demoFirstPercent = Constants.NO_CENTER_DEMO_FIRST;
                            demoLastPercent = Constants.NO_CENTER_DEMO_LAST;
                        }

                        //开户的来源媒介模拟开户数
                        this.userTrackDataAttributionMapAdd(userTrackDataAttributionMap, firstVisitDataDetail.getStartTime(),
                                firstVisitDataDetail.getUtmcsr(), firstVisitDataDetail.getUtmcmd(), demoFirstPercent * demoNum, realFirstPercent * realNum);


                        //最后一次即模拟与真实开户归因
                        this.userTrackDataAttributionMapAdd(userTrackDataAttributionMap, dayTime, userTrackDataDetail.getUtmcsr(),
                                userTrackDataDetail.getUtmcmd(), demoLastPercent * demoNum, realLastPercent * realNum);
                    }
                }else{
                    //有模拟开户的真实开户数计算规则
                    realFirstPercent = Constants.DEMO_REAL_FIRST;
                    realCenterPercent = Constants.DEMO_REAL_CENTER;
                    realLastPercent = Constants.DEMO_REAL_LAST;
                    //模拟、真实开户分开归因第一次访问与中间访问

                    /*
                    *判断第一次访问与现在的来源媒介访问是否为同一条
                    * 如果为同一条，则记录本次来源媒介的真实开户数为1
                    */
                    boolean flag = false;
                    if(userTrackDataDetail.getId() == firstVisitDataDetail.getId()){
                        //开户的来源媒介模拟开户数
                        this.userTrackDataAttributionMapAdd(userTrackDataAttributionMap, dayTime, userTrackDataDetail.getUtmcsr(),
                                userTrackDataDetail.getUtmcmd(), demoNum, 0);
                        flag = true;//本次会话完成归因
                    }
                    if(userTrackDataDetail.getId() == firstDemoDataDetail.getId()){
                        //开户的来源媒介模拟开户数
                        this.userTrackDataAttributionMapAdd(userTrackDataAttributionMap, dayTime, userTrackDataDetail.getUtmcsr(),
                                userTrackDataDetail.getUtmcmd(), 0, realNum);
                        flag = true;//本次会话完成归因
                    }
                    if(!flag) {
                        //中间访问，模拟开户数归因
                        boolean noCenterDemoFlag = this.centerDataDetailAttribute(userTrackDataAttributionMap, firstVisitDataDetail, userTrackDataDetail, Constants.DEMO_CENTER, demoNum, 0, 0);
                        //中间访问，真实开户数归因
                        boolean noCenterRealFlag = this.centerDataDetailAttribute(userTrackDataAttributionMap, firstDemoDataDetail, userTrackDataDetail, 0, 0, realCenterPercent, realNum);

                        if(!noCenterDemoFlag){
                            demoFirstPercent = Constants.NO_CENTER_DEMO_FIRST;
                            demoLastPercent = Constants.NO_CENTER_DEMO_LAST;
                        }
                        if(!noCenterRealFlag){
                            realFirstPercent = Constants.NO_CENTER_DEMO_REAL_FIRST;
                            realLastPercent = Constants.NO_CENTER_DEMO_REAL_LAST;
                        }

                        //第一次访问，模拟开户数归因
                        this.userTrackDataAttributionMapAdd(userTrackDataAttributionMap, firstVisitDataDetail.getStartTime(),
                                firstVisitDataDetail.getUtmcsr(), firstVisitDataDetail.getUtmcmd(), demoFirstPercent * demoNum, 0);
                        //第一次模拟，真实开户数归因
                        this.userTrackDataAttributionMapAdd(userTrackDataAttributionMap, firstDemoDataDetail.getStartTime(),
                                firstDemoDataDetail.getUtmcsr(), firstDemoDataDetail.getUtmcmd(), 0, realFirstPercent * realNum);

                        //最后一次即模拟与真实开户归因
                        this.userTrackDataAttributionMapAdd(userTrackDataAttributionMap, dayTime, userTrackDataDetail.getUtmcsr(),
                                userTrackDataDetail.getUtmcmd(), demoLastPercent * demoNum, realLastPercent * realNum);
                    }
                }

            }
            detailIdList.add(userTrackDataDetail.getId());
        }


        /*
         *修改详细汇总数据表（user_track_data_detail）中的is_attributed表示，表明该条已经被归因过
         */
        userTrackDataDetailService.batchUpdate(detailIdList);

        /*
        *持久化归因列表集合 userTrackDataAttributionMap
        *通过 日期、来源、媒介 判断数据库中是否存在，若存在则更新，反之新增
        */
        List<UserTrackDataAttribution> insertList = new ArrayList<UserTrackDataAttribution>();
        List<UserTrackDataAttribution> updateList = new ArrayList<UserTrackDataAttribution>();
        for(String key : userTrackDataAttributionMap.keySet()){
            UserTrackDataAttribution u = userTrackDataAttributionMap.get(key);
            //判断是否存在，通过日期、来源、媒介在数据库中查找
            Map paramMap = new HashMap();
            paramMap.put("dataTime",DateUtil.getDateFormat(u.getDataTime(),"yyyy-MM-dd"));
            paramMap.put("utmcsr",u.getUtmcsr());
            paramMap.put("utmcmd",u.getUtmcmd());
            UserTrackDataAttribution old = (UserTrackDataAttribution)defaultService.load("queryAttributionByDateAndUtmcsrAndUtmcmd",paramMap);
            if(old == null) {
                insertList.add(u);
            } else {
                double newDemoCount = new BigDecimal(old.getDemoCount() + u.getDemoCount()).setScale(4,BigDecimal.ROUND_HALF_UP).doubleValue();
                double newRealCount = new BigDecimal(old.getRealCount() + u.getRealCount()).setScale(4,BigDecimal.ROUND_HALF_UP).doubleValue();
                old.setDemoCount(newDemoCount);
                old.setRealCount(newRealCount);
                updateList.add(old);
            }
        }
        userTrackDataAttributionService.insertList(insertList);
        userTrackDataAttributionService.batchUpdateList(updateList);


    }


    /**
     * 把归因数据放入map集合中，如果已存在相同的数据，则模拟开户数、真实开户数数据相加
     * key由”日期_来源_媒介“组成
     *
     * @param map       归因集合
     * @param date      归因日期
     * @param utmcsr    来源
     * @param utmcmd    媒介
     * @param demoCount 模块开户数
     * @param realCount 真实开户数
     */
    public void userTrackDataAttributionMapAdd(Map<String, UserTrackDataAttribution> map, Date date,
                                               String utmcsr, String utmcmd, double demoCount, double realCount) {
        if (date == null || (demoCount == 0 && realCount == 0) )
            return;
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        String dateTime = simpleDateFormat.format(date);
        try {
            date = simpleDateFormat.parse(dateTime);
        } catch (ParseException e) {
            Log.error(UserTrackDataAttributionTask.class, "不可能出现的日期异常");
            return;
        }
        String key = dateTime + "_" + utmcsr + "_" + utmcmd;
        if (map.containsKey(key)) {
            UserTrackDataAttribution old = map.get(key);
            double newDemoCount = new BigDecimal(old.getDemoCount() + demoCount).setScale(4,BigDecimal.ROUND_HALF_UP).doubleValue();
            double newRealCount = new BigDecimal(old.getRealCount()  + realCount).setScale(4,BigDecimal.ROUND_HALF_UP).doubleValue();
            old.setDemoCount(newDemoCount);
            old.setRealCount(newRealCount);
            map.put(key, old);
        } else {
            UserTrackDataAttribution userTrackDataAttribution = new UserTrackDataAttribution();
            userTrackDataAttribution.setDataTime(date);
            userTrackDataAttribution.setUtmcsr(utmcsr);
            userTrackDataAttribution.setUtmcmd(utmcmd);
            double newDemoCount = new BigDecimal(demoCount).setScale(4,BigDecimal.ROUND_HALF_UP).doubleValue();
            double newRealCount = new BigDecimal(realCount).setScale(4,BigDecimal.ROUND_HALF_UP).doubleValue();
            userTrackDataAttribution.setDemoCount(newDemoCount);
            userTrackDataAttribution.setRealCount(newRealCount);
            map.put(key, userTrackDataAttribution);
        }

    }


    /**
     * 给中间访问归因
     * @param userTrackDataAttributionMap 归因列表集合
     * @param first 第一次访问或模拟开户
     * @param last 最后一次模拟开户或真实开户
     * @param demoNum 一次会话中模拟开户的个数
     * @param realNum 一次会话中真实开户的个数
     * @return 归因列表集合
     */
    public boolean centerDataDetailAttribute(Map<String,UserTrackDataAttribution> userTrackDataAttributionMap,
                                                                          UserTrackDataDetail first,UserTrackDataDetail last,double demoPercent,
                                                                          int demoNum,double realPercent,int realNum){
        //中间访问数据
        //需要判断第一次访问与开户时的时间差是否在30天内，计算出开始统计的时间
        //多加个一秒，防止出现会话时长为0的情况，即开始时间=结束时间
        String startTime = DateUtil.getDateFormat(first.getStartTime(),"yyyy-MM-dd HH:mm:ss");
        if(first.getStartTime().before(DateUtil.addDays(last.getStartTime(),-Constants.DAY_NUMBER))){
            //如果第一次访问时间比模拟开户时间往前推30天还小，则计算最近30天内的访问数据
            startTime = DateUtil.getDateFormat(DateUtil.addDays(last.getStartTime(),-Constants.DAY_NUMBER),"yyyy-MM-dd HH:mm:ss");
        }

        String endTime = DateUtil.getDateFormat(last.getStartTime(),"yyyy-MM-dd HH:mm:ss");

        Map paramMap = new HashMap();
        paramMap.put("userId",last.getUserId());
        paramMap.put("startTime",startTime);
        paramMap.put("endTime",endTime);
        paramMap.put("id",first.getId());//计算从第一次访问到来源媒介访问时，除去第一次访问

        List<UserTrackDataDetail> centerList = (List<UserTrackDataDetail>)defaultService.find("queryDataDetailByUserIdAndStartEndTime",paramMap);

        if(centerList == null || centerList.size() == 0)
           return false;

        //计算中间访问的平均模拟开户数
        BigDecimal demoBigDecimal = new BigDecimal(demoPercent/centerList.size());
        BigDecimal realBigDecimal = new BigDecimal(realPercent/centerList.size());

        double demoCenterCountAvg = demoBigDecimal.setScale(4,BigDecimal.ROUND_HALF_UP).doubleValue();
        double realCenterCountAvg = realBigDecimal.setScale(4,BigDecimal.ROUND_HALF_UP).doubleValue();

        //循环中间访问，加入归因集合中
        for (UserTrackDataDetail u : centerList){
            userTrackDataAttributionMapAdd(userTrackDataAttributionMap,u.getStartTime(),u.getUtmcsr(),u.getUtmcmd(),demoCenterCountAvg * demoNum,realCenterCountAvg * realNum);
        }

        return true;
    }


}



































